{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "TFE Workshop: control flow",
      "version": "0.3.2",
      "provenance": [],
      "include_colab_link": true
    }
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "[View in Colaboratory](https://colab.research.google.com/gist/alextp/664b2f8700485ff6801f4d26293bd567/tfe-workshop-control-flow.ipynb)"
      ]
    },
    {
      "metadata": {
        "id": "9BpQzh9BvJlj",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 37
        },
        "outputId": "0b336886-8204-4815-89fa-5291a49d5784"
      },
      "cell_type": "code",
      "source": [
        "import tensorflow as tf\n",
        "import numpy as np\n",
        "tf.enable_eager_execution()"
      ],
      "execution_count": 1,
      "outputs": []
    },
    {
      "metadata": {
        "id": "0roIB19GvOjI",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        "# Eager execution basics\n",
        "\n",
        "When eager execution is enabled TensorFlow immediately executes operations, and Tensors are always available. "
      ]
    },
    {
      "metadata": {
        "id": "jeO8F-V-vN24",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 68
        },
        "outputId": "aeb3bdec-50b7-440d-93d8-5a171f091081"
      },
      "cell_type": "code",
      "source": [
        "t = tf.constant([[1, 2], [3, 4]])\n",
        "t"
      ],
      "execution_count": 2,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<tf.Tensor: id=0, shape=(2, 2), dtype=int32, numpy=\n",
              "array([[1, 2],\n",
              "       [3, 4]], dtype=int32)>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 2
        }
      ]
    },
    {
      "metadata": {
        "id": "Y17RwSFxvlDL",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 68
        },
        "outputId": "cfcc10c7-707b-4997-99b3-a5f382c5166b"
      },
      "cell_type": "code",
      "source": [
        "tf.matmul(t, t)"
      ],
      "execution_count": 3,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<tf.Tensor: id=2, shape=(2, 2), dtype=int32, numpy=\n",
              "array([[ 7, 10],\n",
              "       [15, 22]], dtype=int32)>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 3
        }
      ]
    },
    {
      "metadata": {
        "id": "Dab1bS3TvmRE",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "8a624f3d-a658-4359-c586-1c5f6bf4c8b7"
      },
      "cell_type": "code",
      "source": [
        "# It's also possible to have Python control flow which depends on the value of tensors.\n",
        "if t[0, 0] > 0.5:\n",
        "  print(\"T is bigger\")\n",
        "else:\n",
        "  print(\"T is smaller\")"
      ],
      "execution_count": 4,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "T is bigger\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "metadata": {
        "id": "dPgptJcGwIon",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        },
        "outputId": "c4f27f2b-0848-4475-dde5-2534dac65a5c"
      },
      "cell_type": "code",
      "source": [
        "# Tensors are also usable as numpy arrays\n",
        "np.prod(t)"
      ],
      "execution_count": 6,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "24"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 6
        }
      ]
    },
    {
      "metadata": {
        "id": "p3DTfQXnwXzj",
        "colab_type": "text"
      },
      "cell_type": "markdown",
      "source": [
        "# Exercise\n",
        "\n",
        "The algorithm for bisecting line search is a pretty simple way to find a zero of a continuous scalar function in an interval [a,b] where f(a) and f(b) have different signs. Simply evaluate f((a+b)/2), and narrow the interval by replacing either a or b with (a+b)/2 such that the function when applied on the boundary of the interval still has different signs.\n",
        "\n",
        "Implement a python function `bisecting_line_search(f, a, b, epsilon)` which returns a value such that `tf.abs(f(value)) < epsilon`.\n",
        "\n",
        "One thing to keep in mind: python's `==` opertor is not overloaded on Tensors, so you need to use `tf.equal` to compare for equality."
      ]
    },
    {
      "metadata": {
        "id": "6eq0YuI6ykm5",
        "colab_type": "code",
        "colab": {}
      },
      "cell_type": "code",
      "source": [
        "# Example test harness to get you going\n",
        "\n",
        "def test_f(x):\n",
        "  return x - 0.1234\n",
        "def bisecting_line_search(f, a, b, epsilon):\n",
        "  # Return x such that f(x) <= epsilon.\n",
        "  pass\n",
        "a = tf.constant(0.0)\n",
        "b = tf.constant(1.0)\n",
        "epsilon = tf.constant(0.001)\n",
        "x = bisecting_line_search(test_f, a, b, epsilon)\n"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "metadata": {
        "id": "LcMmEfd_xvej",
        "colab_type": "code",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 170
        },
        "outputId": "f402aa50-8ce3-4416-f755-8bbcd1af7809"
      },
      "cell_type": "code",
      "source": [
        "#@title Double-click to see the solution\n",
        "\n",
        "def bisecting_line_search(f, a, b, epsilon):\n",
        "  f_a = f(a)\n",
        "  f_b = f(b)\n",
        "  probe = (a + b) / 2\n",
        "  f_probe = f(probe)\n",
        "  while tf.abs(f_probe) > epsilon:\n",
        "    if tf.equal(tf.sign(f_probe), tf.sign(f_a)):\n",
        "      a = probe\n",
        "      f_a = f_probe\n",
        "    else:\n",
        "      b = probe\n",
        "      f_b = f_probe\n",
        "    probe = (a + b) / 2\n",
        "    f_probe = f(probe)\n",
        "    print(\"new probe\", probe)\n",
        "  return probe\n",
        "\n",
        "bisecting_line_search(test_f, 0., 1., 0.001)"
      ],
      "execution_count": 8,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "('new probe', 0.25)\n",
            "('new probe', 0.125)\n",
            "('new probe', 0.0625)\n",
            "('new probe', 0.09375)\n",
            "('new probe', 0.109375)\n",
            "('new probe', 0.1171875)\n",
            "('new probe', 0.12109375)\n",
            "('new probe', 0.123046875)\n"
          ],
          "name": "stdout"
        },
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "0.123046875"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 8
        }
      ]
    }
  ]
}
